#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#include "../Utils/Utils.h"
#include "LocalStack.h"

GlobalStackElem* GlobalStack;

void FileCloser(void *file)
{
	fclose((FILE*)file);
}

void InitGlobalStack()
{
	if (NULL == (GlobalStack = (GlobalStackElem*)malloc(sizeof(GlobalStackElem))))
	{
		fprintf(stderr, "Couldn't allocate GlobalStack.");
		exit(1);
	}
	
	GlobalStack->Back = NULL;
	GlobalStack->FunctionName = NULL;
	GlobalStack->LocalStack = NULL;
}

void Start(const char* functionName)
{
	LocalStackElem* localStack = NULL;
	GlobalStackElem* temp = NULL;
	
	if (NULL == (localStack = (LocalStackElem*)malloc(sizeof(LocalStackElem)))
		|| NULL == (temp = (GlobalStackElem*)malloc(sizeof(GlobalStackElem)))
		|| NULL == (temp->FunctionName = (char*)malloc(sizeof(char) * strlen(functionName))))
	{
		fprintf(stderr, "Couln't allocate Stack elements, when starting function %s.", functionName);
		free(localStack);
		free(temp);
		ByeBye();
	}
	
	temp->Back = GlobalStack;
	GlobalStack = temp;
	strcpy(GlobalStack->FunctionName, functionName);
	GlobalStack->LocalStack = localStack;
	localStack->Pointer = NULL;
	localStack->Destructor = NULL;
	localStack->Next = NULL;
	localStack->Back = NULL;
}

void FreeLocalStack()
{
	LocalStackElem* ripper;
	for(; GlobalStack->LocalStack != NULL; ripper = GlobalStack->LocalStack, GlobalStack->LocalStack = GlobalStack->LocalStack->Next, free(ripper))
	{}
}

void DeleteLocalStack()
{
	LocalStackElem* ripper;
	
	for(; GlobalStack->LocalStack->Next != NULL; ripper = GlobalStack->LocalStack, GlobalStack->LocalStack = GlobalStack->LocalStack->Next, free(ripper))
		GlobalStack->LocalStack->Destructor(GlobalStack->LocalStack->Pointer);
}

void ByeBye()
{	
	GlobalStackElem* ripper;
	
	for (;GlobalStack->Back != NULL; ripper = GlobalStack, GlobalStack = GlobalStack->Back, free(ripper->FunctionName), free(ripper))
	{
		DeleteLocalStack();
		free(GlobalStack->LocalStack);
		fprintf(stderr, "Unwinding function:\"%s\"\n", GlobalStack->FunctionName);
	}
	
	free(GlobalStack);
	
	exit(1);
}

void PrintPlace(const char* function, const char* file, int line)
{
	fprintf(stderr, "In function:%s file:%s line:%d", function, file, line);
}

void Safe(void* pointer, void (*destructor) (void*), const char* function, const char* file, int line)
{
	LocalStackElem* newLocal = NULL;
	
	if (pointer == NULL)
	{
		fprintf(stderr, "Can't safeify NULL.");
		PrintPlace(function, file, line);
		ByeBye();
	}
		
	if (NULL == (newLocal = (LocalStackElem*)malloc(sizeof(LocalStackElem))))
	{
		fprintf(stderr, "Can't alocate stack element.");
		PrintPlace(function, file, line);
		ByeBye();
	}
	
	newLocal->Pointer = pointer;
	newLocal->Next = GlobalStack->LocalStack;
	newLocal->Destructor = destructor;
	newLocal->Back = NULL;
	newLocal->Next->Back = newLocal;
	GlobalStack->LocalStack = newLocal;
}

void* New(unsigned size, void (*destructor) (void*), const char* function, const char* file, int line)
{
	void* mem = NULL;
	
	if (NULL == (mem = malloc(size)))
	{
		fprintf(stderr, "Could'nt allocate object of size %d ", size);
		PrintPlace(function, file, line);
		ByeBye();
	}	
	
	Safe(mem = malloc(size), destructor, function, file, line);
	
	return mem;
}

void Delete(void* pointer)
{
	LocalStackElem* iter = NULL;
	
	for (iter = GlobalStack->LocalStack; iter->Next != NULL && iter->Pointer != pointer; iter = iter->Next)
	{}
	
	if (iter->Back != NULL)
		iter->Back->Next = iter->Next;
	
	if (iter->Next != NULL)
		iter->Next->Back = iter->Back;
	
	iter->Destructor(pointer);
}

void End()
{
	GlobalStackElem* ripper;
	
	if (GlobalStack == NULL)
	{
		fprintf(stderr, "GlobalStack error, maybe you didn't initilize()?");
		exit(1);
	}
	
	FreeLocalStack();
	free(GlobalStack->LocalStack);
	ripper = GlobalStack;
	GlobalStack = GlobalStack->Back;
	free(ripper);
}

void MDelete(int num, ...)
{
	int iter = 0;
	va_list list;
  
	for (va_start(list, num), iter = 0; iter < num; iter++)
		Delete(va_arg(list, void*));

	va_end(list);
}
